package telemetry

import (
	"context"
	"errors"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/log/global"
	"go.opentelemetry.io/otel/propagation"
	semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

	"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"

	"go.opentelemetry.io/otel/sdk/log"
	"go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	sdkTrace "go.opentelemetry.io/otel/sdk/trace"
)

func traceProvier(ctx context.Context, r *resource.Resource) (*sdkTrace.TracerProvider, error) {

	exp, err := otlptracegrpc.New(
		ctx,
		otlptracegrpc.WithInsecure(),
	)
	if err != nil {
		return nil, err
	}

	return sdkTrace.NewTracerProvider(
		sdkTrace.WithBatcher(exp),
		sdkTrace.WithResource(r),
	), nil
}

func meterProvider(ctx context.Context, r *resource.Resource) (*metric.MeterProvider, error) {
	exp, err := otlpmetricgrpc.New(
		ctx,
		otlpmetricgrpc.WithInsecure(),
	)
	if err != nil {
		return nil, err
	}

	return metric.NewMeterProvider(
		metric.WithResource(r),
		metric.WithReader(metric.NewPeriodicReader(
			exp,
			// Default is 1m. Set to 3s for demonstrative purposes.
			metric.WithInterval(3*time.Second),
		)),
	), nil
}

func logProvider(ctx context.Context, r *resource.Resource) (*log.LoggerProvider, error) {
	exp, err := otlploggrpc.New(
		ctx,
		otlploggrpc.WithInsecure(),
	)
	if err != nil {
		return nil, err
	}
	processor := log.NewSimpleProcessor(exp)
	return log.NewLoggerProvider(
		log.WithProcessor(processor),
		log.WithResource(r),
	), nil
}
func newPropagator() propagation.TextMapPropagator {
	return propagation.NewCompositeTextMapPropagator(
		propagation.TraceContext{},
		propagation.Baggage{},
	)
}
func SetupTelemetry(ctx context.Context, serviceName string) (func(context.Context) error, error) {
	var _f []func(context.Context) error
	shutdown := func(ctx context.Context) error {
		var err error
		for _, fn := range _f {
			err = errors.Join(err, fn(ctx))
		}
		_f = nil
		return err
	}

	// Ensure default SDK resources and the required service name are set.
	r, err := resource.Merge(
		resource.Default(),
		resource.NewWithAttributes(
			semconv.SchemaURL,
		),
	)
	if err != nil {
		return nil, err
	}

	// Set up propagator.
	prop := newPropagator()
	otel.SetTextMapPropagator(prop)

	// 链路追踪
	tp, err := traceProvier(ctx, r)
	if err != nil {
		return shutdown, errors.Join(err, shutdown(ctx))
	}
	_f = append(_f, tp.Shutdown)
	otel.SetTracerProvider(tp)

	// metric 收集
	mp, err := meterProvider(ctx, r)
	if err != nil {
		return shutdown, errors.Join(err, shutdown(ctx))
	}
	otel.SetMeterProvider(mp)
	_f = append(_f, mp.Shutdown)

	// log 收集
	lp, err := logProvider(ctx, r)
	if err != nil {
		return shutdown, errors.Join(err, shutdown(ctx))
	}
	global.SetLoggerProvider(lp)
	_f = append(_f, lp.Shutdown)

	return shutdown, nil
}
